from flask import Flask, request, send_from_directory
from flask_restx import Api, Resource, fields, Namespace
from werkzeug.utils import secure_filename
from werkzeug.datastructures import FileStorage
import os
import uuid
import comtypes.client
import time
import pythoncom
import traceback
import logging

# 配置日志
logging.basicConfig(level=logging.DEBUG, 
                   format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
app.config['OUTPUT_FOLDER'] = 'output'
app.config['ALLOWED_EXTENSIONS'] = {'doc', 'docx', 'xls', 'xlsx', 'ppt', 'pptx'}
app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024  # 限制上传文件大小为50MB

# 确保上传和输出目录存在
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
os.makedirs(app.config['OUTPUT_FOLDER'], exist_ok=True)

# 初始化API文档
api = Api(
    app,
    version='1.0',
    title='Office转PDF API',
    description='一个功能强大的Office文档转PDF的RESTful API服务',
    doc='/docs/',  # API文档路径
    prefix='/api/v1'
)

# 创建命名空间
convert_ns = Namespace('convert', description='文档转换相关接口')
download_ns = Namespace('download', description='文件下载相关接口')
health_ns = Namespace('health', description='健康检查接口')

api.add_namespace(convert_ns)
api.add_namespace(download_ns)
api.add_namespace(health_ns)

# 定义响应模型
upload_response_model = api.model('UploadResponse', {
    'success': fields.Boolean(required=True, description='操作是否成功'),
    'message': fields.String(description='响应消息'),
    'filename': fields.String(description='生成的PDF文件名'),
    'download_url': fields.String(description='下载链接'),
    'file_size': fields.Integer(description='文件大小(字节)'),
    'conversion_time': fields.Float(description='转换耗时(秒)')
})

error_response_model = api.model('ErrorResponse', {
    'success': fields.Boolean(required=True, description='操作是否成功', default=False),
    'error': fields.String(required=True, description='错误信息'),
    'error_code': fields.String(description='错误代码')
})

health_response_model = api.model('HealthResponse', {
    'status': fields.String(required=True, description='服务状态'),
    'timestamp': fields.String(required=True, description='检查时间'),
    'version': fields.String(required=True, description='API版本'),
    'uptime': fields.Float(description='运行时间(秒)')
})

# 文件上传解析器
upload_parser = api.parser()
upload_parser.add_argument('file', location='files', type=FileStorage, required=True, 
                          help='要转换的Office文档文件 (支持: .doc, .docx, .xls, .xlsx, .ppt, .pptx)')

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in app.config['ALLOWED_EXTENSIONS']

def convert_to_pdf(input_file, output_file):
    """使用comtypes将Office文档转换为PDF"""
    logger.info(f"开始转换文件: {input_file} 到 {output_file}")
    start_time = time.time()
    pythoncom.CoInitialize()  # 初始化COM
    
    file_ext = input_file.rsplit('.', 1)[1].lower()
    
    try:
        if file_ext in ['doc', 'docx']:
            # Word转PDF
            logger.info("使用Word转换文档")
            word = comtypes.client.CreateObject('Word.Application')
            word.Visible = False
            doc = word.Documents.Open(input_file)
            doc.SaveAs(output_file, FileFormat=17)  # 17代表PDF格式
            doc.Close()
            word.Quit()
        elif file_ext in ['xls', 'xlsx']:
            # Excel转PDF
            logger.info("使用Excel转换文档")
            excel = comtypes.client.CreateObject('Excel.Application')
            excel.Visible = False
            wb = excel.Workbooks.Open(input_file)
            wb.ExportAsFixedFormat(0, output_file)  # 0代表PDF格式
            wb.Close()
            excel.Quit()
        elif file_ext in ['ppt', 'pptx']:
            # PowerPoint转PDF
            logger.info("使用PowerPoint转换文档")
            powerpoint = comtypes.client.CreateObject('Powerpoint.Application')
            powerpoint.Visible = False
            presentation = powerpoint.Presentations.Open(input_file)
            presentation.ExportAsFixedFormat(output_file, 2)  # 2代表PDF格式
            presentation.Close()
            powerpoint.Quit()
        
        conversion_time = time.time() - start_time
        logger.info(f"文件转换成功，耗时: {conversion_time:.2f}秒")
        return True, conversion_time
    except Exception as e:
        logger.error(f"转换错误: {e}")
        logger.error(traceback.format_exc())
        return False, 0
    finally:
        pythoncom.CoUninitialize()  # 清理COM

@health_ns.route('/check')
class HealthCheck(Resource):
    @api.doc('health_check')
    @api.marshal_with(health_response_model)
    def get(self):
        """
        健康检查接口
        检查API服务是否正常运行
        """
        import datetime
        return {
            'status': 'healthy',
            'timestamp': datetime.datetime.now().isoformat(),
            'version': '1.0',
            'uptime': time.time()
        }

@convert_ns.route('/upload')
class ConvertDocument(Resource):
    @api.doc('convert_document')
    @api.expect(upload_parser)
    @api.marshal_with(upload_response_model, code=200, description='转换成功')
    @api.marshal_with(error_response_model, code=400, description='请求错误')
    @api.marshal_with(error_response_model, code=500, description='服务器错误')
    def post(self):
        """
        上传Office文档并转换为PDF
        
        支持的文件格式:
        - Microsoft Word: .doc, .docx
        - Microsoft Excel: .xls, .xlsx  
        - Microsoft PowerPoint: .ppt, .pptx
        
        文件大小限制: 50MB
        """
        logger.info("收到文件上传请求")
        
        if 'file' not in request.files:
            logger.warning("没有上传文件")
            api.abort(400, '没有上传文件', error_code='NO_FILE')
        
        file = request.files['file']
        
        if file.filename == '':
            logger.warning("未选择文件")
            api.abort(400, '未选择文件', error_code='EMPTY_FILENAME')
        
        if not allowed_file(file.filename):
            logger.warning("不支持的文件类型")
            api.abort(400, '不支持的文件类型。支持格式: .doc, .docx, .xls, .xlsx, .ppt, .pptx', 
                     error_code='UNSUPPORTED_FORMAT')
        
        try:
            # 生成安全的文件名
            filename = secure_filename(file.filename)
            logger.info(f"处理文件: {filename}")
            
            # 添加唯一标识符，避免文件名冲突
            unique_filename = f"{str(uuid.uuid4())}_{filename}"
            
            # 保存原始文件
            file_path = os.path.join(os.path.abspath(app.config['UPLOAD_FOLDER']), unique_filename)
            file.save(file_path)
            logger.info(f"文件已保存到: {file_path}")
            
            # 创建输出PDF的路径
            pdf_filename = f"{os.path.splitext(unique_filename)[0]}.pdf"
            pdf_path = os.path.join(os.path.abspath(app.config['OUTPUT_FOLDER']), pdf_filename)
            
            # 转换文件
            success, conversion_time = convert_to_pdf(file_path, pdf_path)
            
            if success:
                logger.info(f"文件转换成功: {pdf_path}")
                
                # 获取文件大小
                file_size = os.path.getsize(pdf_path) if os.path.exists(pdf_path) else 0
                
                return {
                    'success': True,
                    'message': '文档转换成功',
                    'filename': pdf_filename,
                    'download_url': f'/api/v1/download/file/{pdf_filename}',
                    'file_size': file_size,
                    'conversion_time': round(conversion_time, 2)
                }
            else:
                logger.error("文件转换失败")
                api.abort(500, '文件转换失败，请检查文件格式或稍后重试', error_code='CONVERSION_FAILED')
                
        except Exception as e:
            logger.error(f"处理文件时发生错误: {e}")
            logger.error(traceback.format_exc())
            api.abort(500, f'服务器内部错误: {str(e)}', error_code='INTERNAL_ERROR')

@download_ns.route('/file/<filename>')
class DownloadFile(Resource):
    @api.doc('download_file')
    @api.param('filename', 'PDF文件名', required=True)
    def get(self, filename):
        """
        下载转换后的PDF文件
        
        通过文件名下载之前转换生成的PDF文件
        """
        try:
            return send_from_directory(app.config['OUTPUT_FOLDER'], filename, as_attachment=True)
        except FileNotFoundError:
            api.abort(404, '文件不存在', error_code='FILE_NOT_FOUND')

@convert_ns.route('/formats')
class SupportedFormats(Resource):
    @api.doc('get_supported_formats')
    def get(self):
        """
        获取支持的文件格式列表
        
        返回API支持转换的所有Office文档格式
        """
        return {
            'supported_formats': {
                'word': ['.doc', '.docx'],
                'excel': ['.xls', '.xlsx'],
                'powerpoint': ['.ppt', '.pptx']
            },
            'max_file_size': '50MB',
            'description': '支持Microsoft Office文档转换为PDF格式'
        }

# 添加CORS支持（可选）
@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
    return response

if __name__ == '__main__':
    print("=" * 60)
    print("🚀 Office转PDF API服务启动")
    print("=" * 60)
    print("📖 API文档地址: http://localhost:5002/docs/")
    print("🔗 API根路径: http://localhost:5002/api/v1/")
    print("💡 支持格式: .doc, .docx, .xls, .xlsx, .ppt, .pptx")
    print("=" * 60)
    app.run(debug=True, port=5002)  # 使用5002端口避免冲突 